/*
 * Copyright (C) 2008-2011 CBDP Consortium
 * 
 * All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.
 * 
 * This software consists of contributions made by many individuals, 
 * listed below:
 *
 * Author: 	Pierre Bretault <pierre.bretault@atego.com>
 */
package cbdp.server.contextreasoner.impl;

import static cbdp.server.contextreasoner.impl.util.DebugUtils.printlnIfInDebugModel;

import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

import cbdp.server.contextreasoner.ContextManagerWithoutExecutionSupport;
import cbdp.server.contextreasoner.exceptions.ContextManagerException;
import cbdp.server.contextreasoner.impl.util.NullChecker;
import cbdp.server.contextreasoner.internal.OntologyContainer;

import com.hp.hpl.jena.ontology.ConversionException;
import com.hp.hpl.jena.ontology.DatatypeProperty;
import com.hp.hpl.jena.ontology.HasValueRestriction;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.ObjectProperty;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.Restriction;
import com.hp.hpl.jena.rdf.listeners.StatementListener;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.shared.Lock;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

/**
 * @author  bretault
 */
public class ActionStatementListener extends StatementListener implements ServiceTrackerCustomizer {
    
    private BundleContext bc;
    /**
	 * @uml.property  name="cm"
	 * @uml.associationEnd  
	 */
    private ContextManagerWithoutExecutionSupport cm;
    private OntologyContainer oc;
    /* eventAdmin */
    private ServiceTracker ea_tracker;
    private EventAdmin ea = null;
    
    public ActionStatementListener(BundleContext bc, ContextManagerWithoutExecutionSupport cm, OntologyContainer oc) {
        this.bc = bc;
        this.cm = cm;
        this.oc = oc;
        /* find the EventAdmin */
        ea_tracker = new ServiceTracker(bc, EventAdmin.class.getName(), this);
        ea_tracker.open();
        
        checkRemainingHasCommand();
    }

    private void checkRemainingHasCommand(){
        System.out.println("checkRemainingHasCommand");
        this.oc.getOntModel().enterCriticalSection(Lock.WRITE);
        
        try
        {
            for (final String ns : cm.getOntologyURIs().values()){
                final Property p = this.oc.getOntModel().getProperty(ns + "#hasCommand", "");
                System.out.println("Property : "+p.toString());
                final List<Statement> stmts = this.oc.getOntModel().listStatements(null, null, (String)null, (String)null).toList();

                for(final Statement stmt: stmts)
                {
                    if(stmt.getPredicate().getLocalName().equals("hasCommand")){    
                        printlnIfInDebugModel("WARNING statement " + stmt + " p= "+stmt.getObject().toString()+ " should be removed");
                        this.oc.getOntModel().remove(stmt);
                    }
                }
            }
        }
        catch(Exception e){
            e.printStackTrace();
        }
        finally
        {
            this.oc.getOntModel().leaveCriticalSection();
        }
    }
    
    public void finalize(){
        ea_tracker.close();
    }
    
    @Override
    public void addedStatement(Statement s) {
    	printlnIfInDebugModel("[CM] Ontology Changed : " + s.toString());
        if(this.ea != null){
            this.oc.getOntModel().enterCriticalSection(Lock.READ);
            try{
                Property prop = s.getPredicate();
                String hasCom = prop.getLocalName();
                if(!hasCom.equals("hasCommand"))
                    return;

                // get Functionality Instance
                Individual funcInstance = this.oc.getOntModel().getIndividual(s.getSubject().toString());
                NullChecker.checkNullResource(funcInstance, funcInstance.toString()+" individual not found");

                ObjectProperty isFunc = this.oc.getOntModel().getObjectProperty(funcInstance.getNameSpace()+"isFunctionalityOf");
                NullChecker.checkNullResource(isFunc, isFunc.toString()+" ObjectProperty not found");
                
                Individual actuatorInstance = funcInstance.getPropertyValue(isFunc).as(Individual.class);
                NullChecker.checkNullResource(actuatorInstance, actuatorInstance.toString()+" individual not found");
                
                // get Ontology Name from URI
                String ontologyName = "";
                for (Map.Entry<String,String> e : this.cm.getOntologyURIs().entrySet()){
                    String ns = e.getValue()+"#";
                    if(ns.equals(actuatorInstance.getNameSpace())){
                        e.getKey();
                        ontologyName = e.getKey();
                        break;
                    }
                }
                if(ontologyName.equals("")){
                    throw (new ContextManagerException("No ontolgy associated with the URI : "+ actuatorInstance.getURI() +" in the ontology - URI mapping file"));
                }
                
                // get related Commands instance and data
                if(!s.getObject().isResource()){
                    throw (new ContextManagerException(s.getObject().toString() + " is not a Commands" ));
                }
                
                Individual comInstance = this.oc.getOntModel().getIndividual(s.getObject().toString());
                NullChecker.checkNullResource(comInstance, s.getObject().toString()+" individual not found");
                printlnIfInDebugModel("[AL] the command individual is "+comInstance.getLocalName());               
                
                // Create Internal event to send to the S/A Layer
                Dictionary<String, Object> eventProperties = new Hashtable<String, Object>();
                eventProperties.put("ontology.name", ontologyName);
                eventProperties.put("class.hierarchy", actuatorInstance.getOntClass(true).getLocalName());
                eventProperties.put("instance.id", actuatorInstance.getLocalName());
                
                // get the value of the realCommandName of the considered Commands Individuals
                OntClass comClass = null;
                for (ExtendedIterator<OntClass> i = comInstance.listOntClasses(true); i.hasNext(); ) {
                    OntClass c = (OntClass) i.next();
                    if(!c.getLocalName().equals("Thing")){
                        comClass = c;
                        break;
                    }
                  }
                                
                HashMap<String, String> tmpProp = new HashMap<String, String>();
                final List<Statement> propList = comInstance.listProperties().toList();
                int rcnIndex = 1;
                for (final Statement p : propList){
                    try{
                        printlnIfInDebugModel( "[AL] "+ Integer.toString(rcnIndex) + "- Property :" + p.toString() + " - " + p.getPredicate().as(DatatypeProperty.class).getLocalName() + " -- " + p.getObject().toString());
                        tmpProp.put(p.getPredicate().as(DatatypeProperty.class).getLocalName(),p.getObject().toString());
                        eventProperties.put("action.id."+Integer.toString(rcnIndex), p.getPredicate().as(DatatypeProperty.class).getLocalName());
                        eventProperties.put("action.value."+Integer.toString(rcnIndex), p.getObject().toString());
                        rcnIndex++;
                    }
                    catch(ConversionException e){
                        printlnIfInDebugModel( "[AL]  Property :" + p.getPredicate().toString() + " is not a DatatypeProperty");
                    }
                }
                printlnIfInDebugModel("[AL] The Command sub-class is "+comClass.getLocalName());
                final List<OntClass> ontClassList = comClass.listSuperClasses( true ).toList();
                for (final OntClass c : ontClassList){
                    if (c.isRestriction()) {
                      Restriction r = c.asRestriction();
                      if (r.isHasValueRestriction()) {
                        HasValueRestriction av = r.asHasValueRestriction();
                        Boolean overLoaded = false;
                        try{
                            if(tmpProp.get(av.getOnProperty().getLocalName()) != null ){
                               overLoaded = true; 
                            }
                        }
                        catch(NullPointerException e){}
                        if(!overLoaded){
                            printlnIfInDebugModel( "[AL] "+ Integer.toString(rcnIndex) + "- HasValue :" + av.getHasValue().as(Literal.class).getValue().toString() + " on property " + av.getOnProperty().getLocalName() );
                            eventProperties.put("action.id."+Integer.toString(rcnIndex), av.getOnProperty().getLocalName());
                            eventProperties.put("action.value."+Integer.toString(rcnIndex), av.getHasValue().as(Literal.class).getValue().toString());
                            rcnIndex++;
                        }
                      }
                    }
                }
                
                if(eventProperties.get("action.id.1") == null)
                    printlnIfInDebugModel("[AL] ERROR : Unable to retrieve at least one DatatypeProperty of "+ comInstance.toString());
                
                // Send event to the S/A Layer
                Event e = new Event(
                                    "cbdp/internal/salayer/updatedAction",
                                    eventProperties);
                printlnIfInDebugModel("[AL]: Send Internal Event : "+e.toString());
                this.ea.sendEvent(e);
                
            }
            catch(Exception e){
                e.printStackTrace();
                return;
            }
            finally
            {
                this.oc.getOntModel().leaveCriticalSection();
            }
            
            this.oc.getOntModel().enterCriticalSection(Lock.WRITE);
            try{
                // Remove the statement as its modification doesn't allow to call addedStatement()
                // TODO: managing timestamps of such a statement in the ontology in order to avoid this operation
                this.oc.getOntModel().remove(s);
            }
            finally{
                this.oc.getOntModel().leaveCriticalSection();
            }
        }
    }

    @Override
    public Object addingService(ServiceReference ref) {
        if (ref != null){
        	printlnIfInDebugModel("[AL] : Event Admin discovered ");
            this.ea = (EventAdmin) this.bc.getService(ref);
        }
        return null;
    }

    @Override
    public void modifiedService(ServiceReference arg0, Object arg1) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void removedService(ServiceReference arg0, Object arg1) {
        // TODO Auto-generated method stub
        
    }
    
}
